﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace common4gp
{

    /// <summary>
    /// Common extensions methods
    /// </summary>
    public static class Extensions
    {


        #region Null Extensions


        /// <summary>
        /// Check for null <see cref="System.Object"/>
        /// </summary>
        /// <param name="obj"><see cref="System.Object"/> to check</param>
        /// <returns>True if the object is null</returns>
        public static bool IsNull(this object obj)
        {
            return obj == null;
        }


        /// <summary>
        /// Check for not null <see cref="System.Object"/>
        /// </summary>
        /// <param name="obj"><see cref="System.Object"/> to check</param>
        /// <returns>True if the object is not null</returns>
        public static bool IsNotNull(this object obj)
        {
            return obj != null;
        }


        /// <summary>
        /// Check for a null or empty collection
        /// </summary>
        /// <param name="collection"><see cref="System.Collections.ICollection"/> to check</param>
        /// <returns>True if the <see cref="System.Collections.ICollection"/> is null or empty</returns>
        public static bool IsNullOrEmpty(this ICollection collection)
        {
            return collection.IsNull() || collection.Count == 0;
        }


        /// <summary>
        /// Check for a null Expression
        /// </summary>
        /// <param name="exp"><see cref="System.Linq.Expressions.Expression"/> to check</param>
        /// <returns>True if the <see cref="System.Linq.Expressions.Expression"/> is null</returns>
        public static bool IsNull(this Expression exp)
        {
            return exp == null;
        }

        /// <summary>
        /// Check for a not null Expression
        /// </summary>
        /// <param name="exp"><see cref="System.Linq.Expressions.Expression"/> to check</param>
        /// <returns>True if the <see cref="System.Linq.Expressions.Expression"/> is not null</returns>
        public static bool IsNotNull(this Expression exp)
        {
            return exp != null;
        }

        #endregion


        #region String Extensions
        
        
        /// <summary>
        /// Truncate a <see cref="System.String"/> to a specific length.
        /// </summary>
        /// <remarks>
        /// If the string is shorter then the requested lengh, the result will be the same passed string
        /// </remarks>
        /// <example>
        /// <code>"1234567".Truncate(10);</code> -> "1234567"
        /// <code>"1234567".Truncate(5);</code> -> "12..."
        /// <code>"123".Truncate(2);</code> "12"
        /// </example>
        /// <param name="obj"><see cref="System.String"/> to truncate</param>
        /// <param name="maxLength">Max lenght of the result string</param>
        /// <returns>The truncated <see cref="System.String"/></returns>
        public static string Truncate(this string obj, int maxLength)
        {
            string result = obj;
            if (obj.IsNotNull() &&
                obj.Length > maxLength)
            {
                if (obj.Length > 3 &&
                    maxLength > 3)
                {
                    result = obj.Substring(0, maxLength - 3) + "...";
                }
                else
                {
                    result = obj.Substring(0, maxLength);
                }
            }
            return result;
        }
        
        
        #endregion


        #region Number extensions


        /// <summary>
        /// Check if an object is a number
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to check</param>
        /// <returns>True if the <see cref="System.Object"/> is a number</returns>
        public static bool IsNumber(this object obj)
        {
            Contract.Assert(Contract.Result<bool>().IsNotNull());
            return obj is byte ||
                   obj is sbyte ||
                   obj is short ||
                   obj is ushort ||
                   obj is int ||
                   obj is uint ||
                   obj is long ||
                   obj is ulong ||
                   obj is float ||
                   obj is double ||
                   obj is decimal;
        }



        #endregion


        #region Exceptions Extensions



        /// <summary>
        /// Get a detailed message for an <see cref="System.Exception"/>
        /// </summary>
        /// <param name="exception"><see cref="System.Exception"/> to get the message for</param>
        /// <param name="includeInnerExceptions">True if the message has to contain the inner exceptions</param>
        /// <returns>A detailed message of the exception</returns>
        public static string DetailedMessage(this Exception exception, bool includeInnerExceptions = true)
        {
            Contract.Requires<ArgumentNullException>(exception.IsNotNull());
            Contract.Ensures(Contract.Result<string>().IsNotNull());
            int totalSize = 80;
            int titleSize = 20;
            int descSize = totalSize - titleSize - 3;
            string line = string.Format("+{0}+{1}+", new string('-', titleSize), new string('-', descSize));
            string spaces = new string(' ', titleSize);
            StringBuilder exInformation = new StringBuilder();
            exInformation.AppendLine(line);
            addExceptionInfoLine(exInformation, 
                                 "EXCEPTION", 
                                 string.Format("Type: {0}", exception.GetType().ToString()), 
                                 titleSize,
                                 descSize);
            exInformation.AppendLine(line);
            foreach (PropertyInfo info in exception.GetType().GetProperties())
            {
                if (!string.Equals(info.Name, "InnerException", StringComparison.CurrentCultureIgnoreCase))
                {
                    var value = info.GetValue(exception, null);
                    if (value.IsNotNull())
                    {
                        addExceptionInfoLine(exInformation,
                                             string.Empty,
                                             string.Format("{0}: {1}", info.Name, value.ToString()),
                                             titleSize,
                                             descSize);
                        exInformation.AppendLine(line);
                    }
                }
            }
            if (exception.InnerException.IsNotNull())
            {
                addExceptionInfoLine(exInformation,
                                     "INNER EXCEPTION",
                                     string.Empty,
                                     titleSize,
                                     descSize);
                exInformation.AppendLine(exception.InnerException.DetailedMessage());
            }
            return exInformation.ToString();
        }


        /// <summary>
        /// Add an information of the Exception to the detailed message
        /// </summary>
        /// <param name="builder"><see cref="System.Text.StringBuilder"/> to write into</param>
        /// <param name="title">Title</param>
        /// <param name="message">Message</param>
        /// <param name="titleSize">Size of the title</param>
        /// <param name="messageSize">Size of the message</param>
        private static void addExceptionInfoLine(StringBuilder builder, string title, string message, int titleSize, int messageSize)
        {
            string titleToAdd = title;
            string messageToAdd = message;
            while (true)
            {
                builder.AppendLine(string.Format("|{0}|{1}|", titleToAdd.PadRight(titleSize),
                                                              messageToAdd.Replace(Environment.NewLine, " ").PadRight(messageSize)));
                if (titleToAdd.Length <= titleSize &&
                    messageToAdd.Length <= messageSize)
                {
                    break;
                }
                // Remain title
                if (titleToAdd.Length > titleSize)
                {
                    titleToAdd = titleToAdd.Substring(titleSize);
                }
                else
                {
                    titleToAdd = string.Empty;
                }
                // Remain message
                if (messageToAdd.Length > messageSize)
                {
                    messageToAdd = messageToAdd.Substring(messageSize);
                }
                else
                {
                    messageToAdd = string.Empty;
                }
            }
        }


        #endregion


    }
}
